/**
 * Copyright 1998-2006, CHISEL Group, University of Victoria, Victoria, BC, Canada.
 * All rights reserved.
 */
package ca.uvic.cs.cogz.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import ca.uvic.cs.cogz.mapping.filter.TreeFilterManager;
import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Frame;
import edu.stanford.smi.protege.model.KnowledgeBase;
import edu.stanford.smi.protege.model.Slot;
import edu.stanford.smi.protegex.owl.model.OWLModel;
import edu.stanford.smi.protegex.owl.model.OWLNAryLogicalClass;
import edu.stanford.smi.protegex.owl.model.RDFProperty;
import edu.stanford.smi.protegex.owl.model.RDFSClass;
import edu.stanford.smi.protegex.owl.model.RDFSNamedClass;
import edu.stanford.smi.protegex.owl.model.impl.DefaultOWLIndividual;

/**
 * Utility class for performing different Frame related operations.
 * 
 * @author Sean Falconer
 * @date 10-Jan-07
 */
public class FrameUtil {
	private static Map<KnowledgeBase, Map<Frame, Set<Slot>>> domainRangeRelsBuffer;
	
	public static String cleanFrameName(String frameName) {
		if(frameName.startsWith("http:")) {
			frameName = frameName.substring(frameName.indexOf("#")+1);
		}
		
		return frameName;
	}
	
	/**
	 * Gets the slots associated with the given class based on domain/range property restrictions.
	 */
	public static Collection<Slot> getDomainRangeSlots(Cls cls) {
		createDomainRangeRels(cls.getKnowledgeBase());
		
		Collection<Slot> slots = domainRangeRelsBuffer.get(cls.getKnowledgeBase()).get(cls);
		if(slots == null) slots = new ArrayList<Slot>();

		return slots;
	}
	
	/**
	 * Creates a map from all the domain classes to their associated RDFProperty restrictions.
	 * @param knowledgeBase
	 */
	@SuppressWarnings("unchecked")
	private static void createDomainRangeRels(KnowledgeBase knowledgeBase) {
		if(domainRangeRelsBuffer != null && domainRangeRelsBuffer.get(knowledgeBase) != null) return;
		
		if(domainRangeRelsBuffer == null) {
			domainRangeRelsBuffer = new HashMap<KnowledgeBase, Map<Frame,Set<Slot>>>();
		}
		
		Map<Frame, Set<Slot>> domainRange = new HashMap<Frame, Set<Slot>>();
		domainRangeRelsBuffer.put(knowledgeBase, domainRange);

		OWLModel owlModel = (OWLModel) knowledgeBase;
		Collection<RDFProperty> allProperties = (Collection<RDFProperty>)owlModel.getRDFProperties();
		for (RDFProperty property : allProperties) {
			// convert domain values into named classes
			List domainVals = new ArrayList(property.getDomains(true));
			List rangeDefs = new ArrayList(property.getRanges(true));

			// if either the domain or range is specified, but not both, then add owl:THING
			if (domainVals.isEmpty() && !rangeDefs.isEmpty()) {
				domainVals.add(owlModel.getOWLThingClass());
			} else if (rangeDefs.isEmpty() && !domainVals.isEmpty()) {
				rangeDefs.add(owlModel.getOWLThingClass());
			}
			Set domains = new HashSet();
			for (Iterator iterator = domainVals.iterator(); iterator.hasNext();) {
				Frame domain = (Frame) iterator.next();
				if (domain instanceof RDFSNamedClass) {
					domains.add(domain);
				} else if (domain instanceof OWLNAryLogicalClass) {
					//domains.add(domain);
					domains.addAll(((OWLNAryLogicalClass) domain).getNamedOperands()); // TODO is this really what should be shown?
				} else {
				//	System.err.println("domain not a RDFSNamedClass or OWLNAryLogicalClass: " + domain.getBrowserText());
				}
			}

			//convert range definitions into named classes
			Set ranges = new HashSet();
			for (Iterator rangeDefIter = rangeDefs.iterator(); rangeDefIter.hasNext();) {
				Frame range = (Frame) rangeDefIter.next();
				if (range instanceof RDFSNamedClass) {
					ranges.add(range);
				} else if (range instanceof OWLNAryLogicalClass) {
					//ranges.add(range);
					ranges.addAll(((OWLNAryLogicalClass) range).getNamedOperands()); // TODO is this really what should be shown?
				} else {
					//System.err.println("range not a RDFSNamedClass or OWLNAryLogicalClass: " + range.getBrowserText());
				}
			}
			
			// make relationships between all named classes in the domain and all named classes in the range
			for (Iterator iterator = domains.iterator(); iterator.hasNext();) {
				RDFSClass domainClass = (RDFSClass) iterator.next();
				
				Set<Slot> slots = domainRange.get(domainClass);
				if(slots == null) slots = new HashSet<Slot>();
				
				slots.add(property);
				domainRange.put(domainClass, slots);
			}
		}
	}
	
	/**
	 * Returns all frames that have been bound to match the search criteria or are an ancestor of a frame matching the search criteria.
	 */
	@SuppressWarnings("unchecked")
	public static Collection getSearchMatchedFrames(Collection allFrames) {
		Collection<Frame> interestingFrames = new ArrayList<Frame>();
		for (Object obj : allFrames) {
			Frame frame = getFrameFromObject(obj);
			if (frame != null) {
				if (TreeFilterManager.getInstance().isMatch(frame)) {
					interestingFrames.add(frame);
				}
			}
		}

		return interestingFrames;
	}

	/**
	 * Converts the given object into a Frame object.
	 */
	@SuppressWarnings("unchecked")
	public static Frame getFrameFromObject(Object obj) {
		Frame frame = null;
		if (obj instanceof Frame) {
			frame = (Frame) obj;
		} else if (obj instanceof ArrayList) {
			frame = (Frame) (((ArrayList) obj).get(0));
		}

		return frame;
	}

	/**
	 * Compares the actual ordering of the frames given their common parent item.
	 */
	@SuppressWarnings("unchecked")
	public static int compareOrder(Frame f1, Frame f2, Frame parentFrame) {
		KnowledgeBase kb = f1.getKnowledgeBase();

		Cls parentCls;
		if (parentFrame == null) {
			parentCls = kb.getRootCls();
		} else {
			parentCls = (Cls) parentFrame;
		}

		for (Iterator iter = parentCls.getDirectSubclasses().iterator(); iter.hasNext();) {
			Cls subclass = (Cls) iter.next();
			if (subclass.equals(f1)) {
				return -1;
			} else if (subclass.equals(f2)) {
				return 1;
			}
		}

		return 0;
	}

	public static Cls getCls(Object o) {
		if (o instanceof Cls) {
			return (Cls) o;
		} else if (o instanceof DefaultOWLIndividual) {
			return ((DefaultOWLIndividual) o).getDirectType();
		}

		return null;
	}
}
